-- AchievementM
-- Create by panyl
-- 迷宫事件模块

module("AchievementM", package.seeall);

-- 迷宫事件配置表
local achievementTable = {};

-- 迷宫事件奖励配置表
local achievementBonusTable = {};

-- 根据规则存放配置
local listByRule = {};

-- 根据迷宫编号存放配置
local listByDungeonId = {};

-- 所有的子规则
local rules = {};

-- 本文件名
local PURE_FILE = "AchievementM";

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    achievementTable = {};
    achievementBonusTable = {};
    listByRule = {};
    listByDungeonId = {};

    -- 转换迷宫事件配置表
    local achievement_CSV = get_achievement_CSV();
    for i = 1, #achievement_CSV do
        local id = achievement_CSV[i].id;
        achievementTable[id] = achievement_CSV[i];

        -- 根据规则存放
        local rule = achievement_CSV[i].rule;
        if listByRule[rule] == nil then
            listByRule[rule] = {};
        end
        table.insert(listByRule[rule], id);

        -- 根据迷宫编号存放
        local dungeonId = achievement_CSV[i].dungeon_id;
        if listByDungeonId[dungeonId] == nil then
            listByDungeonId[dungeonId] = {};
        end
        table.insert(listByDungeonId[dungeonId], id);
    end

    achievement_CSV = {};

    -- 转换迷宫事件奖励配置表
    local achievement_bonus_CSV = get_achievement_bonus_CSV();
    for i = 1, #achievement_bonus_CSV do
        local dungeonId = achievement_bonus_CSV[i].dungeon_id;

        -- 解析奖励
        achievementBonusTable[dungeonId] = parseBonus(achievement_bonus_CSV[i]);
    end
end

-- 模块初始化
function init()
    if not _init then
        loadCsv();

        -- 载入所有的规则处理子模块
        rules = LOAD_PATH("game/logic/module/achievements");
    end
end

-- 查询迷宫事件信息
function query(id, path)
    local m = achievementTable[id];

    if path == "" or path == nil then
        return m;
    else
        return m[path];
    end
end

-- 解析迷宫事件奖励
function parseBonus(info)
    local bonusCfg = info["bonus"];
    for dp, _ in pairs(bonusCfg) do
        local str = bonusCfg[dp];

        -- 多个奖励直接用'+'分隔
        local strArr = string.explode(str, "+");
        local bonusList = {};
        for _, desc in pairs(strArr) do
            local arr = string.explode(string.sub(desc, 3, -2), ",");
            if string.startWith(desc, "1(") then
                -- 物品奖励
                table.insert(bonusList, { 1, tonumber(arr[1]), tonumber(arr[2]) });
            elseif string.startWith(desc, "2(") then
                -- 属性奖励
                table.insert(bonusList, { 2, arr[1], tonumber(arr[2]) });
            else
                error("暂不支持的奖励配置。");
            end
        end

        bonusCfg[dp] = bonusList;
    end

    -- 解析迷宫扫荡加成奖励
    local propBonusCfg = info["prop_bonus"];
    for dp, _ in pairs(propBonusCfg) do
        local str = propBonusCfg[dp];

        -- 多个奖励直接用'+'分隔
        local strArr = string.explode(str, "+");
        local propBonusList = {};
        for _, desc in pairs(strArr) do
            local arr = string.explode(string.sub(desc, 2, -2), ",");
            if string.startWith(desc, "(") then
                -- 物品奖励
                table.insert(propBonusList, { arr[1], tonumber(arr[2]), tonumber(arr[3]) });
            else
                error("暂不支持的奖励配置。");
            end
        end

        propBonusCfg[dp] = propBonusList;
    end

    return info;
end

-- 根据规则获取迷宫事件id
function getIdsByRule(id)
    return listByRule[id] or {};
end

-- 根据迷宫编号获取迷宫事件id
function getIdsByDungeonId(dungeonId)
    local parentId = DungeonAreaM.getParentId(dungeonId);
    if not listByDungeonId[parentId] then
        return {};
    end

    -- 这是主关卡，默认返回所有迷宫事件
    if parentId == dungeonId then
        return listByDungeonId[dungeonId];
    end

    local ret = {};
    for _, id in pairs(listByDungeonId[dungeonId]) do
        if query(id, "only_main_area") ~= 1 then
            table.insert(ret, id);
        end
    end
    return ret;
end

-- 查询迷宫迷宫事件奖励
function queryBonus(dungeonId)
    if not achievementBonusTable[dungeonId] then
        -- 不存在该迷宫的迷宫事件奖励
        return nil;
    end

    return achievementBonusTable[dungeonId]["bonus"];
end

-- 查询迷宫，扫荡加成奖励事件
function queryPropBonus(dungeonId)
     if not achievementBonusTable[dungeonId] then
        -- 不存在该迷宫的迷宫事件奖励
        return nil;
    end

    return achievementBonusTable[dungeonId]["prop_bonus"];
end

-- 获取可领取奖励的dp值（按照从小到大升序）
function queryBonusDps(dungeonId)
    local bonus = queryBonus(dungeonId);
    if not bonus then
        return {};
    end

    -- 升序
    local ret = table.keys(bonus);
    table.sort(ret);

    return ret;
end

-- 判断是否是该迷宫的迷宫事件
function isBelongTo(id, dungeonId)
    local config = query(id);
    if not config then
        return false;
    end

    if config["only_main_area"] == 1 then
        -- 该迷宫事件仅对主关卡开放
        return config["dungeon_id"] == dungeonId;
    else
        local parentId = DungeonAreaM.getParentId(dungeonId);
        return config["dungeon_id"] == parentId;
    end
end

-- 获取玩家迷宫事件进度
function getProgress(id)
    local m = ME.user.achievements[id];

    if m == nil then
        -- 没有数据就返回0
        return 0;
    end

    -- 返回进度
    return m.progress;
end

-- 更新一个迷宫事件的进度
function setProgress(id, progress)
    local m = ME.user.achievements[id];

    if m == nil then
        -- 新增一个迷宫事件数据
        m = { ["progress"] = progress, }
    else
        m.progress = progress
    end

    ME.user.achievements[id] = m;

    -- 如果已经完成了，抛出事件
    if isCompleted(id) then
        EventMgr.fire(event.ACHIV_COMPLETE, { ["id"] = id, });
    end

    -- 更新成就
    EventMgr.fire(event.UPDATE_ACHIEVEMENT, {["id"] = id, ["progress"] = progress, });
end

function setAchievements(buff)
    local buffer = Buffer.create(buff);
    local offset = 1;
    local id = 0;
    local size = Buffer.size(buffer);

    -- 先清理下旧的数据
    ME.user.achievements = {};

    -- 这个是玩家身上的成就数据
    local achievements = ME.user.achievements;

    while (offset <= size) do
        -- 一个成就进度占1个字节
        local progress = Buffer.getu8(buffer, offset);

        if AchievementM.query(id) then
            achievements[id] = { ["progress"] = progress, }
        end

        -- 继续下一个成就
        id = id + 1;
        offset = offset + 1;
    end
end

-- 打包成就数据（verify to gs）
function packAchievements(filter)
    local ret = {__mapping = 1, };
    for id, data in pairs(ME.user.achievements) do
        if data["progress"] > 0 and (not filter[id] or filter[id]["progress"] ~= data["progress"]) then
            ret[id] = data["progress"];
        end
    end

    return ret;
end

-- 判断某dp的奖励是否已被领取
function isBonusTaken(dungeonId, dpIndex)
    local record = getBonusRecord(dungeonId);
    if not record then
        return false;
    end

    if record[dpIndex] == nil then
        return false;
    end

    return true;
end

-- 获取某个迷宫的奖励领取记录
function getBonusRecord(dungeonId)
    local achiementBonus = ME.user.achievements_bonus or {};
    return achiementBonus[dungeonId];
end

-- 尝试完成迷宫事件
function tryComplete(id, count)
    if not query(id) then
        return false;
    end

    -- 获取迷宫事件当前进度
    local curProgress = getProgress(id);

    -- 控制进度超过上限
    local maxProgress = query(id, "progress");
    local newProgress = math.min(maxProgress, curProgress + count);

    -- 更新进度
    setProgress(id, newProgress);

    return true;
end

-- 获取子规则
function queryRule(rule)
    if rules[rule] == "TBL" then
        rules[rule] =  LOAD_RUN("game/logic/module/achievements/" .. rule);
    end

    return rules[rule]
end

-- 获取迷宫事件描述
function queryDesc(id)
    local config = query(id);
    local desc = config["desc"];

    -- 获取需求进度
    local progress = config["progress"];

    -- 获取当前进度
    local curProgress = getProgress(id);

    local f = queryRule(config["rule"]);
    if not f then
        assert(false, string.format("子模块%s不存在。", config["rule"]));
    end

    -- 调用子模块的接口获取
    if f.queryDesc then
        desc = f.queryDesc(id);
    end

    -- 如果只需要完成1次，不需要显示进度
    if progress <= 1 then
        return desc;
    end

    -- 将进度和描述拼接起来
    desc = string.format("%s %d/%d", desc, curProgress, progress);

    return desc
end

-- 判断某个迷宫事件是否已经完成了
function isCompleted(id)
    -- 获取当前进度
    local curProgress = getProgress(id);

    -- 获取需求进度
    local progress = query(id, "progress");

    if not progress then
        return false;
    end

    return curProgress >= progress;
end

-- 计算某个迷宫当前已累积的dp值
function calcCurDp(dungeonId)
    local achievements = ME.user.achievements;
    local curDp = 0;
    for id, _ in pairs(achievements) do
        local config = query(id) or {} ;

        -- 只有已经完成的迷宫事件才可获得dp值
        if config["dungeon_id"] == dungeonId and
           isCompleted(id) then
            curDp = curDp + config["dp"];
        end
    end
    return curDp;
end

-- 计算当前所有关卡累积的dp值
function calcAreasCumulateDp()
    -- 获取所有的主关卡
    local areas = DungeonAreaM.getMainAreas();

    local totalDp = 0;
    for id, _ in pairs(areas) do
        totalDp = totalDp + calcCurDp(id);
    end

    return totalDp;
end

-- 计算某个迷宫的总dp值
function calcTotalDp(dungeonId)
    local totalDp = 0;
    for _, row in pairs(achievementTable) do
        if row["dungeon_id"] == dungeonId then
            totalDp = totalDp + row["dp"];
        end
    end
    return totalDp;
end

-- 计算某个迷宫当前可领取的迷宫事件奖励
function calcBonus(dungeonId)
    local bonusCfg = queryBonus(dungeonId);
    if type(bonusCfg) ~= 'table' then
        return {};
    end

    -- 计算该迷宫当前已累积的dp值
    local curDp = calcCurDp(dungeonId);

    -- 获取可领取奖励的dp值
    local dps = queryBonusDps(dungeonId);

    -- 统计本次可领取的奖励
    local bonusList = {};
    local ret = {};
    for index = 1, #dps do
        -- dp值已达成，并且未领取奖励
        local dp = dps[index];
        if curDp >= dp and
           not isBonusTaken(dungeonId, index) then
            bonusList = table.add(bonusList, bonusCfg[dp]);
            ret =  { ["dpIndex"] = index, ["bonus"] = bonusList};
            break;
        end
    end

    return ret;
end

-- 计算当前某一奖励可以领取
function calcBonusByIndex(dungeonId, index)
     local bonusCfg = queryBonus(dungeonId);
    if type(bonusCfg) ~= 'table' then
        return {};
    end

    -- 计算该迷宫当前已累积的dp值
    local curDp = calcCurDp(dungeonId);

    -- 获取可领取奖励的dp值
    local dps = queryBonusDps(dungeonId);

    -- 统计本次可领取的奖励
    local bonusList = {};
    local ret = {};

    -- dp值已达成，并且未领取奖励
    local dp = dps[index];
    if curDp >= dp and
       not isBonusTaken(dungeonId, index) then
        bonusList = table.add(bonusList, bonusCfg[dp]);
        ret =  { ["dpIndex"] = index, ["bonus"] = bonusList};
    end

    return ret;
end

-- 获取下一个可领取奖励的dp值
function getNextBonusDp(dungeonId)
    -- 计算该迷宫当前已累积的dp值
    local curDp = calcCurDp(dungeonId);

    -- 获取可领取奖励的dp值
    local dps = queryBonusDps(dungeonId);

    -- dp值未达到，或者下一级可领取奖励没领
    for i = 1 ,#dps do
        if curDp < dps[i] or
            not isBonusTaken(dungeonId, i) then
            return dps[i];
        end
    end

    -- dp值已经满了且奖励领完了
    return nil;
end

-- 判断能否领取奖励
function canTakeBonus(dungeonId)
    local ret = calcBonus(dungeonId);
    if #table.keys(ret) == 0 then
        return false;
    end

    return true;
end

-- 获取当前可领取奖励的dp值
function getCurBonusDpValue(dungeonId)
    local dps = queryBonusDps(dungeonId);

    -- 计算当前可领取的奖励
    local bonus = calcBonus(dungeonId);
    local dpIndex = bonus.dpIndex;
    if dpIndex ~= nil then
        -- 当前有奖励可领取，返回对应的dp值
        return dps[dpIndex];
    end

    -- 当前没有奖励可领，获取下一个可领取奖励的dp值
    -- 如果没取到，应该是领取了所有的奖励，这里默认返回最后一个dp值
    return AchievementM.getNextBonusDp(dungeonId) or dps[#dps];
end

-- 获取可领取的dp奖励个数
function getAvailableBonusNum(dungeonId)
    local bonusCfg = queryBonus(dungeonId);
    if type(bonusCfg) ~= 'table' then
        return 0;
    end

    -- 计算该迷宫当前已累积的dp值
    local curDp = calcCurDp(dungeonId);

    -- 获取可领取奖励的dp值
    local dps = queryBonusDps(dungeonId);

    -- 统计未领取的dp奖励个数
    local count = 0;
    for index = 1, #dps do
        -- dp值已达成，并且未领取奖励
        local dp = dps[index];
        if curDp >= dp and
            not isBonusTaken(dungeonId, index) then
            count = count + 1;
        end
    end

    return count;
end

-- 领取某个迷宫的迷宫事件奖励
function doBonus(dungeonId, index)
    -- 计算可领取的奖励
    local ret = calcBonusByIndex(dungeonId, index);

    if #table.keys(ret) == 0 then
        trace(PURE_FILE, "迷宫(%d)当前没有可领取的迷宫事件奖励。", dungeonId);
        return false;
    end

    local dpIndex   = ret.dpIndex;   -- dp索引
    local bonusList = ret.bonus;     -- 奖励

    -- 标记奖励为已领取
    local achievementBonus = ME.user.achievements_bonus;
    achievementBonus[dungeonId] = achievementBonus[dungeonId] or {};
    achievementBonus[dungeonId][dpIndex] = 1;
    ME.user.achievements_bonus = achievementBonus;

    -- 给予奖励
    for _, arr in pairs(bonusList) do
        BonusM.doBonus(arr, "dungeon_achiv_bonus");
    end

    trace(PURE_FILE, "成功领取第(%d)个dp值的奖励。", dpIndex);

    -- 抛出事件
    EventMgr.fire(event.TAKE_ACHIV_BONUS, { ["bonus"] = bonusList, });

    return true;
end

-- 全部进度
function isAllCompleted()
    for k, v in pairs(achievementTable) do
        -- 有一个不满足就返回false
        if not isCompleted(k) then
            return false;
        end
    end
    return true;
end

-- 是否达到所需DP值
function isReachDp(needDp)
    local curDp = 0;

    for k, v in pairs(achievementTable) do
        if curDp >= needDp then
            return true;
        end
        -- 有一个不满足就返回false
        if isCompleted(k) then
           local dp = query(k, "dp");
           curDp = curDp + dp;
        end
    end

    return false;
end

-- 判断某项DP奖励是否可以领取
function canTakeBonusByIndex(dungeonId, index)
    -- 计算该迷宫当前已累积的dp值
    local curDp = calcCurDp(dungeonId);

    -- 获取可领取奖励的dp值
    local dps = queryBonusDps(dungeonId);

    -- dp值已达成，并且未领取奖励
    local dp = dps[index];
    if dp == nil then
        return false;
    end
    if curDp >= dp and
        not isBonusTaken(dungeonId, index) then
        return true;
    end

    return false;
end

-- 获得扫荡加成描述
function getPropBonusDesc(bonus, areaId)
    local desc = getLocStr("achiv_prop_bonus");
    local bonusType = bonus[1];

    --
    if bonusType == "piece" then
        local mopUpBonus = DungeonAreaM.query(areaId, "mop_up_bonus_args") or {};
        local classId = mopUpBonus.class_id;
        desc = string.gsub(desc, "{prop}", ItemM.query(classId, "name"));
        return desc;
    end

    if bonusType == "eggs" then
        local mopUpBonus = DungeonAreaM.query(areaId, "mop_up_bonus_args") or {};
        local slimeId = mopUpBonus.slime;
        local classId = PetM.query(slimeId, "chips");
        desc = string.gsub(desc, "{prop}", ItemM.query(classId, "name"));
        return desc;
    end

    -- 食材加成
    if bonusType == "food" then
        desc = string.gsub(desc, "{prop}", getLocStr("food_material"));
        return desc;
    end

    -- 完成日常任务
    if bonusType == "finish_task" then
        return getLocStr("mop_up_finish_task");
    end

    -- 物品奖励加成
    local itemName = ItemM.query(tonumber(bonusType), "name");
    if itemName then
        desc = string.gsub(desc, "{prop}", itemName);
        return desc;
    end

    -- 如果是属性加成
    if FieldsM.getFieldName(bonusType) then
        if "gem" == bonusType then
            desc = getLocStr("mop_up_gem");
            return desc;
        end
        if "exp" == bonusType then
            desc = string.gsub(desc, "{prop}", getLocStr("mop_up_exp"));
            return desc;
        end
        local attribName = FieldsM.getFieldName(bonusType);
        desc = string.gsub(desc, "{prop}", attribName);
        return desc;
    end
end

-- 获得当前迷宫所有已领取领取的奖励prop_bonus属性
function getDungoenPropBonus(dungeonId)

    -- 获取可领取奖励的dp值
    local dps = queryBonusDps(dungeonId);
    local propBonusCfg = queryPropBonus(dungeonId);
    -- 统计本次可领取的奖励
    local bonusList = {};
    local ret = {};
    for index = 1, #dps do
        local dp = dps[index];
        if isBonusTaken(dungeonId, index) then
            -- 添加判断，因为有的dp领奖只是领取物品奖励
            if type(propBonusCfg[dp]) == "table" then
                bonusList = table.add(bonusList, propBonusCfg[dp]);
            end
        end
    end

    return bonusList;
end

-- 判断是否领取了奖励
function hasFinishTaskProp(dungeonId)
    local propBonusList = getDungoenPropBonus(dungeonId) or {};

    -- 如果有则finish_task属性奖励返回true
    for k, v in pairs(propBonusList) do
        if v[3] == -3 and v[1] == "finish_task" then
            return true;
        end
    end

    return false;
end
